package cn.bird.storm.component.jackson;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class StormJackson2ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
    private static final String dateFormat = "yyyy-MM-dd";
    private static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    private static final String timeFormat = "HH:mm:ss";
    private static final Map<?, Boolean> FEATURE_DEFAULTS;
    private final ApplicationContext applicationContext;
    private final JacksonProperties jacksonProperties;

    static {
        Map<Object, Boolean> featureDefaults = new HashMap<>();
        featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
    }

    public StormJackson2ObjectMapperBuilderCustomizer(ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
        this.applicationContext = applicationContext;
        this.jacksonProperties = jacksonProperties;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void customize(Jackson2ObjectMapperBuilder builder) {
        if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
            builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
        }

        if (this.jacksonProperties.getTimeZone() != null) {
            builder.timeZone(this.jacksonProperties.getTimeZone());
        }
        builder.simpleDateFormat(dateTimeFormat);
        builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
        builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
        builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
        builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
        builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
        builder.deserializers(new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
        this.configureFeatures(builder, FEATURE_DEFAULTS);
        this.configureVisibility(builder, this.jacksonProperties.getVisibility());
        this.configureFeatures(builder, this.jacksonProperties.getDeserialization());
        this.configureFeatures(builder, this.jacksonProperties.getSerialization());
        this.configureFeatures(builder, this.jacksonProperties.getMapper());
        this.configureFeatures(builder, this.jacksonProperties.getParser());
        this.configureFeatures(builder, this.jacksonProperties.getGenerator());
        this.configureDateFormat(builder);
        this.configurePropertyNamingStrategy(builder);
        this.configureModules(builder);
        this.configureLocale(builder);
    }

    private void configureFeatures(Jackson2ObjectMapperBuilder builder, Map<?, Boolean> features) {
        features.forEach((feature, value) -> {
            if (value != null) {
                if (value) {
                    builder.featuresToEnable(new Object[]{feature});
                } else {
                    builder.featuresToDisable(new Object[]{feature});
                }
            }

        });
    }

    private void configureVisibility(Jackson2ObjectMapperBuilder builder, Map<PropertyAccessor, JsonAutoDetect.Visibility> visibilities) {
        visibilities.forEach(builder::visibility);
    }

    private void configureDateFormat(Jackson2ObjectMapperBuilder builder) {
        String dateFormat = this.jacksonProperties.getDateFormat();
        if (dateFormat != null) {
            try {
                Class<?> dateFormatClass = ClassUtils.forName(dateFormat, (ClassLoader) null);
                builder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass));
            } catch (ClassNotFoundException var6) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
                TimeZone timeZone = this.jacksonProperties.getTimeZone();
                if (timeZone == null) {
                    timeZone = (new ObjectMapper()).getSerializationConfig().getTimeZone();
                }

                simpleDateFormat.setTimeZone(timeZone);
                builder.dateFormat(simpleDateFormat);
            }
        }

    }

    private void configurePropertyNamingStrategy(Jackson2ObjectMapperBuilder builder) {
        String strategy = this.jacksonProperties.getPropertyNamingStrategy();
        if (strategy != null) {
            try {
                this.configurePropertyNamingStrategyClass(builder, ClassUtils.forName(strategy, (ClassLoader) null));
            } catch (ClassNotFoundException var4) {
                this.configurePropertyNamingStrategyField(builder, strategy);
            }
        }

    }

    private void configurePropertyNamingStrategyClass(Jackson2ObjectMapperBuilder builder, Class<?> propertyNamingStrategyClass) {
        builder.propertyNamingStrategy((PropertyNamingStrategy) BeanUtils.instantiateClass(propertyNamingStrategyClass));
    }

    private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder builder, String fieldName) {
        Field field = ReflectionUtils.findField(PropertyNamingStrategy.class, fieldName, PropertyNamingStrategy.class);
        Assert.notNull(field, () -> {
            return "Constant named '" + fieldName + "' not found on " + PropertyNamingStrategy.class.getName();
        });

        try {
            builder.propertyNamingStrategy((PropertyNamingStrategy) field.get((Object) null));
        } catch (Exception var5) {
            throw new IllegalStateException(var5);
        }
    }

    private void configureModules(Jackson2ObjectMapperBuilder builder) {
        Collection<Module> moduleBeans = getBeans(this.applicationContext, Module.class);
        builder.modulesToInstall(moduleBeans.toArray(new Module[0]));
    }

    private void configureLocale(Jackson2ObjectMapperBuilder builder) {
        Locale locale = this.jacksonProperties.getLocale();
        if (locale != null) {
            builder.locale(locale);
        }

    }

    private static <T> Collection<T> getBeans(ListableBeanFactory beanFactory, Class<T> type) {
        return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type).values();
    }
}
